/*
 *             DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
                    Version 2, December 2004

 Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>

 Everyone is permitted to copy and distribute verbatim or modified
 copies of this license document, and changing it is allowed as long
 as the name is changed.

            DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

  0. You just DO WHAT THE FUCK YOU WANT TO. 
*/
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <stdbool.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#include <setjmp.h>
#include <sys/wait.h>

#include "toplevel.h"
#include "environ.h"
#include "editline.h"
#include "tokenize.h"
#include "intern.h"
#include "history.h"

void (*sigBack)(int);
int stdin_backup, stdout_backup;

void sigHandl(int s)
{
    close(STDOUT_FILENO);
    dup(stdout_backup);
    close(STDIN_FILENO);
    dup(stdin_backup);

    printf("\n");
}

/**
 * Execute un programme
 */
void doexec(int argc, char *argv[], bool dowait)
{
    int i;
    pid_t pid;

    // ou pas
    if(argc == 0)
        return;

    // est-ce une commande interne ?
    internal_cmd cmd = find_internal(argv[0]);

    // création processus
    if(cmd == NULL && (pid = fork()) == 0)
    {
        // fils
        char *envpath = get_environ("PATH");
        char *tok = strdup(envpath), *p;

        // on cherche après le programme dans le path
        for(p = strtok_r(envpath, ":;", &tok); p; p = strtok_r(NULL, ":;", &tok))
        {
            // on écrit les arguments
            char path[PATH_MAX];
            char *args[argc + 1];
            for(i = 0; i < argc; i++)
                args[i] = argv[i];
            args[argc] = NULL;
            snprintf(path, PATH_MAX, "%s/%s", p, args[0]);

            // on tente d'excecuter
            execve(path, args, Environ); // return -1 si erreur, on continue la boucle pour tenter un autre path. si ça marche, le processus est remplacé et donc on a plus à se soucier de la boucle
        }
        fprintf(stderr, "command not found: %s\n", argv[0]);
        exit(0);
    }
    else
    {
        // père

        // execute la commande interne si il s'agit d'une commande interne
        if(cmd)
            cmd(argc, argv);

        if(dowait)
        {
            int status;
            waitpid(pid, &status, 0);
            fflush(stdout);
        }
    }
}

char *getshellline(FILE *f, char *buffer, int size)
{
    char *line;

    if(f == stdin)
    {
        if((line = editline(buffer, size, get_environ("PROMPT"))))
            add_history(line);
    }
    else
    {
        line = fgets(buffer, size, f);
    }

    return line;
}

void toplevel(FILE *f)
{
    int i;
    struct token *t;
    char buffer[512];
    stdin_backup = dup(fileno(stdin));
    stdout_backup = dup(fileno(stdout));
    
    sigBack = signal(SIGINT, sigHandl);

    while(1)
    {
        int pfd[2];
        int fdin = -1,
            fdout = -1;

        char *argv[255];
        bool doloop;
        int argc = 0;
        char *line = getshellline(f, buffer, sizeof(buffer));
        if(!line)
            return;

        for(doloop = true, t = get_token(buffer); doloop; t = get_token(NULL))
        {
            struct token *tok = t;

            // prépare une commande
            switch(tok->type)
            {
                case item: /* push item */
                    argv[argc++] = tok->content;
                    break;
                case less: /* stdin <= out fichier */
                    tok = get_token(NULL);
                    fdin = open(tok->content, O_RDONLY, 0);
                    break;
                case greater: /* stdout => in fichier */
                case append: /* stdout => in fichier(append) */
                    tok = get_token(NULL);
                    fdout = open(tok->content, O_WRONLY | (tok->type == append ? O_APPEND : O_CREAT | O_TRUNC), S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
                    break;
                case semicolon: /* plusieurs commandes groupées en une ligne */
                    break;
                case pip: /* stdin => pipe => stdout */
                    pipe(pfd);
                    fdout = pfd[1];
                    //printf("pipe (%d => %d)\n", pfd[0], pfd[1]);
                    break;
                case amper: /* lance en tâche de fond */
                    break;
                case eol:
                    doloop = false;
                    break;
                case error:
                    doloop = false;
                    fprintf(stderr, "erreur de ligne de commande : %s\n", tok->content);
                    break;
                default:
                    break;
            }

            // execute une commande si besoin
            switch(tok->type)
            {
                case less:
                case greater:
                case append:
                case semicolon:
                case pip:
                case amper:
                case eol:
                    if(fdin != -1)
                    {
                        close(STDIN_FILENO);
                        dup(fdin);
                        close(fdin);
                    }
                    if(fdout != -1)
                    {
                        close(STDOUT_FILENO);
                        dup(fdout);
                        close(fdout);
                    }

                    //printf("%d => exec(%s) => %d\n", fdin, argv[0], fdout);
                    doexec(argc, argv, (tok->type == amper || tok->type == pip) ? false : true);

                    if(fdout != -1)
                    {
                        fdin = pfd[0];
                    }
                    else
                    {
                        close(STDIN_FILENO);
                        dup(stdin_backup);
                        fdin = -1;
                    }
                    if(fdout != -1)
                    {
                        close(STDOUT_FILENO);
                        dup(stdout_backup);
                        fdout = -1;
                    }
                    argc = 0;

                    if(tok->type != pip)
                        i++;
                    break;
                default:
                    break;
            }
        }
    }
}
